package com.vclee.fast.utils;

import com.vclee.fast.utils.exception.Assert;
import com.vclee.fast.utils.model.Bson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.InputSource;

import javax.activation.MimetypesFileTypeMap;
import javax.net.ssl.*;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * Author: sir. Li
 * email:  lesli2@qq.com
 * Date:   2020/11/13
 */
public class URLClient {
    private static final Logger logger = LoggerFactory.getLogger(URLClient.class);

    private static final class DefaultTrustManager implements X509TrustManager {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }
    }


    private static HttpsURLConnection getHttpsURLConnection(String uri, String method) throws IOException {
        SSLContext ctx = null;
        try {
            ctx = SSLContext.getInstance("TLS");
            ctx.init(new KeyManager[0], new TrustManager[]{new DefaultTrustManager()}, new SecureRandom());
        } catch (KeyManagementException | NoSuchAlgorithmException e) {
            logger.error("信任所有https证书错误:" + e.getMessage(), e);
        }
        SSLSocketFactory ssf = Objects.requireNonNull(ctx).getSocketFactory();

        URL url = new URL(uri);
        HttpsURLConnection httpsConn = (HttpsURLConnection) url.openConnection();
        httpsConn.setSSLSocketFactory(ssf);
        httpsConn.setHostnameVerifier((arg0, arg1) -> true);
        httpsConn.setRequestMethod(method);
        httpsConn.setDoInput(true);
        httpsConn.setDoOutput(true);
        httpsConn.setConnectTimeout(5000);
        httpsConn.setReadTimeout(8000);
        return httpsConn;
    }

    private static HttpURLConnection getHttpURLConnection(String uri, String method) throws IOException {
        URL url = new URL(uri);
        HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
        httpConn.setRequestMethod(method);
        httpConn.setDoOutput(true);
        httpConn.setDoInput(true);
        httpConn.setConnectTimeout(5000);
        httpConn.setReadTimeout(8000);
        httpConn.setRequestProperty("Accept-Charset", "UTF-8");
        return httpConn;
    }

    private static byte[] getBytesFromStream(InputStream is) throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        byte[] kb = new byte[1024];
        int len;
        while ((len = is.read(kb)) != -1) bos.write(kb, 0, len);
        byte[] bytes = bos.toByteArray();
        bos.close();
        is.close();
        return bytes;
    }

    private static void setBytesToStream(OutputStream os, byte[] bytes) throws IOException {
        ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
        int len;
        byte[] kb = new byte[1024];
        while ((len = bis.read(kb)) != -1) os.write(kb, 0, len);
        os.flush();
        os.close();
        bis.close();
    }

    public static String doGet(String uri, Bson json) {
        HttpURLConnection httpConn = null;
        if(json==null) json =new Bson();
        StringBuilder builder = new StringBuilder(uri);
        if(!json.keySet().isEmpty()){
            for (Map.Entry<String, Object> entry : json.entrySet()) {
                builder.append("&").append(entry.getKey()).append("=").append(entry.getValue());
            }
        }
        String url = builder.toString().replaceFirst("&","?");
        try {
            if (uri.startsWith("https://")) httpConn = getHttpsURLConnection(url, "GET");
            else httpConn = getHttpURLConnection(url, "GET");
            // logger.info("URLClient的Get请求完成,请求地址:{},响应结果:{}", uri, res);
            return new String(getBytesFromStream(httpConn.getInputStream()), StandardCharsets.UTF_8);
        } catch (IOException e) {
            logger.error("URLClient的Get请求异常,请求地址:{},异常原因:{}", uri, e.getMessage());
        }finally {
            if(httpConn!=null){
                httpConn.disconnect();
                httpConn = null;
            }
        }
        return null;
    }

    public static String doJSONPost(String uri, Object json) {
        HttpURLConnection httpConn = null;
        if (json == null) json = new Bson();
        String data = Bson.toString(json);
        try {
            if (uri.startsWith("https://")) httpConn = getHttpsURLConnection(uri, "POST");
            else httpConn = getHttpURLConnection(uri, "POST");
            httpConn.setRequestProperty("Content-Type", "application/json; charset=utf-8");
            setBytesToStream(httpConn.getOutputStream(), data.getBytes());
            return new String(getBytesFromStream(httpConn.getInputStream()), StandardCharsets.UTF_8);
        } catch (IOException e) {
            logger.error("URLClient的Post请求异常,请求地址:{},参数:{},异常原因:{}", uri, data, e.getMessage());
        }finally {
            if(httpConn!=null){
                httpConn.disconnect();
                httpConn = null;
            }
        }
        return null;
    }

    public static String doFormPost(String uri, Object json) {
        HttpURLConnection httpConn = null;
        if (json == null) json = new Bson();
        String data = Bson.toString(json);
        try {
            StringBuilder builder = new StringBuilder();
            Bson request = Bson.create(data);
            for (Map.Entry<String, Object> entry : request.entrySet())  builder.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
            if(builder.length()>0) builder = new StringBuilder(builder.substring(0, builder.length() - 1));
            if (uri.startsWith("https://")) httpConn = getHttpsURLConnection(uri, "POST");
            else httpConn = getHttpURLConnection(uri, "POST");
            httpConn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
            setBytesToStream(httpConn.getOutputStream(), builder.toString().getBytes());
            return new String(getBytesFromStream(httpConn.getInputStream()), StandardCharsets.UTF_8);
        } catch (IOException e) {
            logger.error("URLClient的doFromPost请求异常,请求地址:{},参数:{},异常原因:{}", uri, data, e.getMessage());
        }finally {
            if(httpConn!=null){
                httpConn.disconnect();
                httpConn = null;
            }
        }
        return null;
    }

    public static String doXmlPost(String uri, String xml) {
        try {
            Assert.claim(!isXmlDocument(xml),"请发送xml文档类型的字符串");
            HttpURLConnection httpConn;
            if (uri.startsWith("https://")) httpConn = getHttpsURLConnection(uri, "POST");
            else httpConn = getHttpURLConnection(uri, "POST");
            httpConn.setRequestProperty("Content-Type", "application/xml; charset=utf-8");
            setBytesToStream(httpConn.getOutputStream(), xml.getBytes());
            // logger.info("URLClient的XML请求完成,请求地址:{},参数:{},响应结果:{}", uri, xml, res);
            return new String(getBytesFromStream(httpConn.getInputStream()), StandardCharsets.UTF_8);
        } catch (IOException e) {
            logger.error("URLClient的XML请求异常,请求地址:{},参数:{},异常原因:{}", uri, xml, e.getMessage());
            return null;
        }
    }


    private static boolean isXmlDocument(String rtnMsg){
        boolean flag = true;
        try {
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder();
            builder.parse(new InputSource(new StringReader(rtnMsg)));
        } catch (Exception e) {
            flag = false;
        }
        return flag;
    }

    /**
     * 上传图片
     * @param urlStr 上传地址
     * @param textMap 文本Map
     * @param fileMap 文件map
     * @param contentType 没有传入文件类型默认采用application/octet-stream
     * contentType非空采用filename匹配默认的图片类型
     * @return 返回response数据
     */
    public static String doFormUpload(String urlStr, Map<String, String> textMap, Map<String, String> fileMap,String contentType){
        List<HttpURLConnection> list = new ArrayList<>();
        try {
            HttpURLConnection conn;
            if (urlStr.startsWith("https://")) conn = getHttpsURLConnection(urlStr, "POST");
            else conn = getHttpURLConnection(urlStr, "POST");
            list.add(conn);
            // boundary就是request头和上传文件内容的分隔符
            String BOUNDARY = "---------------------------123821742118716";
            conn.setConnectTimeout(60000);
            conn.setReadTimeout(60000);
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setUseCaches(false);
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Connection", "Keep-Alive");
            // conn.setRequestProperty("User-Agent","Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.6)");
            conn.setRequestProperty("Content-Type","multipart/form-data; boundary=" + BOUNDARY);
            OutputStream out = new DataOutputStream(conn.getOutputStream());
            // text
            if (textMap != null) {
                StringBuilder strBuf = new StringBuilder();
                for (Map.Entry<String, String> entry : textMap.entrySet()) {
                    String inputName = (String) ((Map.Entry<?, ?>) entry).getKey();
                    String inputValue = (String) ((Map.Entry<?, ?>) entry).getValue();
                    if (inputValue == null) continue;
                    strBuf.append("\r\n").append("--").append(BOUNDARY).append("\r\n");
                    strBuf.append("Content-Disposition: form-data; name=\"").append(inputName).append("\"\r\n\r\n");
                    strBuf.append(inputValue);
                }
                out.write(strBuf.toString().getBytes());
            }
            // file
            if (fileMap != null) {
                for (Map.Entry<String, String> entry : fileMap.entrySet()) {
                    String inputName = (String) ((Map.Entry<?, ?>) entry).getKey();
                    String inputValue = (String) ((Map.Entry<?, ?>) entry).getValue();
                    if (inputValue == null) continue;
                    String filename;
                    DataInputStream in;
                    if(inputValue.startsWith("http")){
                        filename = inputValue.substring(inputValue.lastIndexOf("/") + 1);
                        HttpURLConnection connection;
                        if (inputValue.startsWith("https://")) connection = getHttpsURLConnection(inputValue, "GET");
                        else connection = getHttpURLConnection(inputValue, "GET");
                        connection.connect();
                        in =  new DataInputStream(connection.getInputStream());
                        list.add(connection);
                    }else{
                        File file = new File(inputValue);
                        //没有传入文件类型，同时根据文件获取不到类型，默认采用application/octet-stream
                        contentType = new MimetypesFileTypeMap().getContentType(file);
                        filename = file.getName();
                        in =  new DataInputStream(Files.newInputStream(file.toPath()));
                    }

                    //contentType非空采用filename匹配默认的图片类型
                    if (!"".equals(contentType)) {
                        if (filename.endsWith(".png")) {
                            contentType = "image/png";
                        } else if (filename.endsWith(".jpg") || filename.endsWith(".jpeg") || filename.endsWith(".jpe")) {
                            contentType = "image/jpeg";
                        } else if (filename.endsWith(".gif")) {
                            contentType = "image/gif";
                        } else if (filename.endsWith(".ico")) {
                            contentType = "image/image/x-icon";
                        }
                    }
                    if (contentType == null || contentType.isEmpty()) {
                        contentType = "application/octet-stream";
                    }
                    String strBuf = "\r\n" + "--" + BOUNDARY + "\r\n" +
                            "Content-Disposition: form-data; name=\"" + inputName + "\"; filename=\"" + filename + "\"\r\n" +
                            "Content-Type:" + contentType + "\r\n\r\n";
                    out.write(strBuf.getBytes());
                    int bytes;
                    byte[] bufferOut = new byte[1024];
                    while ((bytes = in.read(bufferOut)) != -1) {
                        out.write(bufferOut, 0, bytes);
                    }
                    in.close();
                }
            }
            byte[] endData = ("\r\n--" + BOUNDARY + "--\r\n").getBytes();
            out.write(endData);
            out.flush();
            out.close();
            // 读取返回数据
            StringBuilder strBuf = new StringBuilder();
            BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String line;
            while ((line = reader.readLine()) != null) {
                strBuf.append(line).append("\n");
            }
            reader.close();
            reader = null;

            return strBuf.toString();
        } catch (Exception e) {
            logger.error("发送POST请求出错,请求地址:{},错误信息:{}",urlStr,e.getMessage(),e);
        } finally {
            for (HttpURLConnection connection : list) {
                if(connection!=null){
                    connection.disconnect();
                    connection = null;
                }
            }
        }
        return null;
    }
}
